commonlibsse_ng\re\c\Calendar/
time.rs

1use super::{day::GameDay, month::MonthIndex, year::Year};
2use chrono::{NaiveDate, NaiveDateTime};
3
4/// NewType wrapper for `NaiveDateTime`, representing in-game date and time.
5///
6/// # Example
7/// ```
8/// use commonlibsse_ng::re::Calendar::{GameDateTime, Year, MonthIndex, GameDay, Hour};
9///
10/// let year = Year::new(2025.0);
11/// let month = MonthIndex::new(2.0);   // March (0-based)
12/// let day = GameDay::new(28.0);
13/// let hour = Hour::new(15.5);         // 15:30
14///
15/// let date_time = GameDateTime::new(year, month, day, hour).unwrap();
16/// assert_eq!(date_time.to_string(), "2025-03-28 15:30:00");
17/// ```
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct GameDateTime(pub NaiveDateTime);
20
21impl Default for GameDateTime {
22    #[inline]
23    fn default() -> Self {
24        Self::DEFAULT
25    }
26}
27
28impl GameDateTime {
29    /// The default in-game date: `77-01-01 00:00:00`.
30    ///
31    /// # Example
32    /// ```
33    /// use commonlibsse_ng::re::Calendar::GameDateTime;
34    /// assert_eq!(GameDateTime::default().to_string(), "0077-01-01 00:00:00");
35    /// ```
36    pub const DEFAULT: Self = Self::from_ymd(77, 1, 1);
37
38    /// Creates a new `GameDateTime` from year, month, day, and hour components.
39    ///
40    /// - The month uses **0-based indexing** internally (`0 = January`, `11 = December`).
41    /// - The day and hour values are clamped to their valid ranges.
42    ///
43    /// Returns `None` if the date or time is invalid.
44    ///
45    /// # Example
46    /// ```
47    /// use commonlibsse_ng::re::Calendar::{GameDateTime, Year, MonthIndex, GameDay, Hour};
48    ///
49    /// let year = Year::new(2025.0);
50    /// let month = MonthIndex::new(2.0);   // March
51    /// let day = GameDay::new(15.0);
52    /// let hour = Hour::new(9.75);         // 9:45
53    ///
54    /// let date_time = GameDateTime::new(year, month, day, hour).unwrap();
55    /// assert_eq!(date_time.to_string(), "2025-03-15 09:45:00");
56    ///
57    /// // Invalid date returns `None`
58    /// let invalid = GameDateTime::new(year, MonthIndex::new(12.0), day, hour);
59    /// assert!(invalid.is_none());
60    /// ```
61    #[inline]
62    pub fn new(year: Year, month: MonthIndex, day: GameDay, hour: Hour) -> Option<Self> {
63        let month = month.to_clamp_month()?; // 1-based month (1..=12)
64        let day = day.to_clamp_day(month); // Clamped day
65        let (hour, minute) = { (hour.to_hour(), hour.to_minutes()) };
66
67        NaiveDate::from_ymd_opt(year.to_year(), month, day)
68            .and_then(|date| date.and_hms_opt(hour, minute, 0))
69            .map(Self)
70    }
71
72    /// Creates a `NaiveDateTime` from year, month, and day components.
73    ///
74    /// This is a helper function to avoid using deprecated `from_ymd` directly.
75    ///
76    /// # Panics
77    /// - Panics if the provided date or time is invalid or out of range.
78    ///
79    /// # Example
80    /// ```
81    /// use commonlibsse_ng::re::Calendar::GameDateTime;
82    ///
83    /// let date = GameDateTime::from_ymd(2025, 3, 27);
84    ///
85    /// // Panics: invalid date
86    /// // let invalid = from_ymd(2025, 13, 32);
87    /// ```
88    pub const fn from_ymd(year: i32, month: u32, day: u32) -> Self {
89        match NaiveDate::from_ymd_opt(year, month, day) {
90            Some(date) => match date.and_hms_opt(0, 0, 0) {
91                Some(time) => Self(time),
92                None => panic!("Invalid time"),
93            },
94            None => panic!("invalid or out-of-range date"),
95        }
96    }
97}
98
99impl core::fmt::Display for GameDateTime {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        write!(f, "{}", self.0)
102    }
103}
104
105/// NewType wrapper for `f32` representing in-game hours(0-based).
106///
107/// Internally stored as a floating-point value:
108/// - `0.0` → `00:00` (midnight)
109/// - `15.5` → `15:30`
110/// - `23.99` → nearly `23:59`
111///
112/// # Example
113/// ```
114/// use commonlibsse_ng::re::Calendar::Hour;
115///
116/// let hour = Hour::new(10.5);         // 10:30 AM
117/// assert_eq!(hour.to_hour(), 10);
118/// assert_eq!(hour.to_minutes(), 30);
119///
120/// let max_hour = Hour::new(23.99);    // Max valid hour
121/// assert_eq!(max_hour.to_hour(), 23);
122/// assert_eq!(max_hour.to_minutes(), 59);
123/// ```
124#[derive(Debug, Default, Clone, Copy, PartialEq)]
125#[repr(transparent)]
126pub struct Hour(f32);
127
128impl Hour {
129    /// Creates a new `Hour` instance.
130    ///
131    /// # Example
132    /// ```
133    /// use commonlibsse_ng::re::Calendar::Hour;
134    /// let hour = Hour::new(9.75);     // 9:45 AM
135    /// assert_eq!(hour.to_hour(), 9);
136    /// assert_eq!(hour.to_minutes(), 45);
137    /// ```
138    #[inline]
139    pub const fn new(value: f32) -> Self {
140        Self(value)
141    }
142
143    /// Returns the hour component (`0..=23`).
144    ///
145    /// # Example
146    /// ```
147    /// use commonlibsse_ng::re::Calendar::Hour;
148    ///
149    /// let hour = Hour::new(14.25);   // 14:15
150    /// assert_eq!(hour.to_hour(), 14);
151    /// ```
152    #[inline]
153    pub const fn to_hour(self) -> u32 {
154        self.0 as u32
155    }
156
157    /// Returns the minute component (`0..=59`).
158    ///
159    /// # Example
160    /// ```
161    /// use commonlibsse_ng::re::Calendar::Hour;
162    ///
163    /// let hour = Hour::new(12.5);    // 12:30 PM
164    /// assert_eq!(hour.to_minutes(), 30);
165    ///
166    /// let almost_full = Hour::new(23.99);  // Nearly 23:59
167    /// assert_eq!(almost_full.to_minutes(), 59);
168    /// ```
169    #[inline]
170    pub fn to_minutes(self) -> u32 {
171        (60.0 * self.0) as u32 % 60
172    }
173}